﻿import RealisticBooks.TextUtil;
import RealisticBooks.BookSection;
import RealisticBooks.BookLine;
import flash.external.ExternalInterface;
import RealisticBooks.BookPage;
import RealisticBooks.BookProperties;
import RealisticBooks.BookLink;
import RealisticBooks.BookImage;
import RealisticBooks.PaginateText;

/**
 * ParseText class
 * This class parse the text and paginates them
 *
 * Algorithm:
 * 1. Read the XML nodes
 * 2. Write it in terms of paragraphs into a page
 * 3. Each paragraphs contains of lines of text
 * 4. For each line we need to update the html text
 *    to match both the flash html text so all
 *    the tag open and close matches plus the additional attribute
 *    of the tag that we store and have the block and font tag
 *
 * @author: Veronica Liesaputra
 */
class RealisticBooks.ParseText {
/**
 * The text field for checking the current html text width and height
 * @type TextField
 */
private var txtField:TextField;
/**
 * The text format for the text field
 * @type TextFormat
 */
private var txtFmt:TextFormat;
/**
 * The current paragraph
 * @type Array
 */
private var currParagraph:Array;
/**
 * The current page
 * @type BookPage
 */
private var currPage:BookPage;
/**
 * The current section
 * @type BookSection
 */
private var currSection:BookSection;
/**
 * The collated start tag
 * @type String
 */
private var start_style:String;
/**
 * The collated end tag
 * @type String
 */
private var end_style:String;
/**
 * The current paragraph alignment
 * @type String
 */
private var text_align:String;
/**
 * The current paragraph leading space
 * @type Number
 */
private var line_leading:Number;
/**
 * Whether the current text belongs to a hyperlink tag
 * @type Boolean
 */
private var isLink:Boolean;
/**
 * The current id for the wiki article
 * @type String
 */
private var wikiID:String;
/**
 * The current word represented by the article with ID = wikiID
 * @type String
 */
private var wikiWord:String;
/**
 * The list of font face allowed in Realistic Books
 * @type Array
 */
private var fontArray:Array;
/**
 * The book toolbar
 */
private var btoolbar:MovieClip;
/**
 * The book properties
 * @type BookProperties
 */
private var bookProp:BookProperties;
/**
 * The last bob item parsed
 * @type Number
 */
private var bobNumber:Number;
/**
 * Initialize the parse text
 * @constructor
 * Parameters:
 * @param {BookProperties} bookProp the properties of the book
 * @param {MovieClip}	   btoolbar	the toolbar for the book
 */
public function ParseText (bookProp:BookProperties,
						   btoolbar:MovieClip) {
	this.currParagraph = new Array();
	this.bookProp = bookProp;
	this.btoolbar = btoolbar;
	this.currPage = new BookPage();
	this.wikiID = "";
	this.wikiWord = "";
	this.isLink = false;
	this.currSection = null
	this.start_style = "";
	this.end_style = "";
	this.text_align = "left";
	this.line_leading = 2;
	this.bobNumber = 0;
	getAllowedFont();
}
/**
 * Initialize the list of font face allowed in Realistic Books
 */
private function getAllowedFont () {
	var allowedFace:Array = ["arial", "courier new", "georgia", "times new roman",
					   		 "verdana"];
	var allowedVariant:Array = [" sup"," sub"," misc"];
	
	this.fontArray = new Array();
	for (var i:Number = 0; i < allowedFace.length; i++) {
		this.fontArray.push(allowedFace[i]);
		for (var j:Number = 0; j < allowedVariant.length; j++) {
			this.fontArray.push(allowedFace[i] + allowedVariant[j]);
		}
	}
}
/**
 * ======================================================================================
 * PARSING TAG
 * ======================================================================================
 */
/**
 * Given the XML nodes, parse the node to get the properties of the book
 * and the content of the book. In this part, we only perform line breaking
 * and making sure building the crude sturcture of the text. All text
 * is placed in one page
 * Parameters:
 * @param {XML} node	current XML/HTML node we want to parse
 * @param {BookSection} section	the current book section we are building
 * @param {TextField}	txtField the text field to test the width and height
 *								 of a text
 * Return:
 * @return {Array} the page containing all the text from the xml
 */
public function parseRoot(node:XML, section:BookSection, 
						  txtField:TextField):String {
	this.txtField = txtField;
	this.txtFmt = txtField.getTextFormat();
	txtField.setNewTextFormat(txtFmt);
	bookProp.setInitBookSize(txtField,txtFmt);
	parseTree(node, section);
	var pagingText:PaginateText = new PaginateText(bookProp);
	var pages:Array = pagingText.paginate(currPage);
	var str = pagingText.outputPages();
	trace(str);
	return str;
}
/**
 * Recursively goes down the XML and HTML nodes, and build the book.
 * Parameters:
 * @param {XMLNode} node	current XML/HTML node we want to parse
 * @param {BookSection} section	the current book section we are building
 * Return:
 * @return {BookSection} the section build
 */
private function parseTree(node:XMLNode, section:BookSection):BookSection {
	for (var i:Number = 0; i < node.childNodes.length; i++) {
		var currNode:XMLNode = node.childNodes[i];//current child node
		var currName:String = (currNode.nodeName).toLowerCase();//the tag name
		
		if ((TextUtil.strIsEmpty(currName,true) == false) && 
			((currName == "metadata") || (currName == "bookmarklist") || 
			 (currName == "annotationlist") || (currName == "newpage") || 
			 (currName == "section") || (currName == "description") ||
			 ((currName.charAt(0) == "h") && 
			  (isNaN(currName.charAt(1)) == false)))) {
			section = parseTreeXMLNode(currNode, section);
		} else {
			section = parseTreeHTMLNode(currNode,section);
		}
	}
	return section;
}
/**
 * Recursively goes down the XML node and build the book. 
 * Parameters:
 * @param {XMLNode} 		currNode	current XML node we want to parse
 * @param {BookSection} section 	current book section we are building
 * Returns:
 * @return {BookSection} the completly parsed section
 */
private function parseTreeXMLNode (currNode:XMLNode, section:BookSection):BookSection {
	//the tag name
	var currName:String = (currNode.nodeName).toLowerCase();

	if ((currName == "bookmarklist") || (currName == "annotationlist")) {
		var attr:Object = currNode.attributes;
		for (var attr_name:String in attr) {
			if (attr_name.toLowerCase() == "pagenum") {
				var val:String = attr[attr_name];
				if ((TextUtil.strIsEmpty(val,true) == false) && 
					(isNaN(val) == false)) {
					//parseBookmarkAnnotation(currNode, Number(val));
				}
			}
		}
	} else if ((currName == "section") || (currName.charAt(0) == "h")) {
		return parseSectionTag(currNode, section);
	} else if (currName == "description") {
		parseDescriptionTag(currNode, section);
	} else if (currName == "newpage") {
		completeCurrParagraph();
		//the current line of the book
		var currLine:BookLine = currParagraph[currParagraph.length - 1];
		currLine.lineNewpage = true;
		currLine.lineText = "<newpage />";
		
		var map:Object = getAttrMapFor (currNode);
		currLine.lineSlidesname = (new Array()).concat(getAttrValue(map, "filename", new Array()));
		completeCurrParagraph();
	}
	
	return section;
}
/**
 * Recursively goes down the HTML node and build the book. 
 * Parameters:
 * @param {XMLNode} 	currNode	current HTML node we want to parse
 * @param {BookSection} section		the current book section we are building
 * Returns:
 * @return {BookSection} the expanded book section
 */
private function parseTreeHTMLNode (currNode:XMLNode, section:BookSection):BookSection {
	var currName:String = (currNode.nodeName).toLowerCase();//the tag name
	var s_attr:String = "";//write the attributes in a string 

	if ((currName == "style") || (currName == "script") || 
		(currName == "link") || (currName == "meta") ||
		(currName == "newlink") || (currName == "newimage")) {
		//do nothing if it is under the ignored tag
	} else if ((currName == "title") && 
			   (TextUtil.strIsEmpty(bookProp.bookTitle,true) == true)) {
		bookProp.bookTitle = currNode.childNodes[0].nodeValue;
	} else if (currName == "newline") { 
		//keep recurse down the tree
		if ((currNode.childNodes[0].nodeName).toLowerCase() != "soc")
			section = parseTree(currNode, section);
	} else if ((currName == "head") || (currName == "body") || 
			   (currName == "html")) {
		//keep recurse down the tree
		section = parseTree(currNode, section);
	} else {
		if (currName == null) {//it's a text tag
			parseTextTag (currNode);
		} else if ((currName == "img") || (currName == "p") || 
				(currName == "br") || (currName == "li") || 
				(currName == "soc") || (currName == "bob") ||
				(currName == "bobitem")) {//it's a paragraph tag
			section = parseParagraphTag (currNode, section);
		} else if ((currName == "u") || (currName == "a") || 
				   (currName == "b") || (currName == "font") || 
				   (currName == "i") || (currName == "span") || 
				   (currName == "sup") || (currName == "sub") ||
				   (currName == "textformat")) {
			section = parseInlineTag (currNode, section);
		} else { //unrecognized html tag
			//keep recurse down the tree
			section = parseTree(currNode, section);
		}
	}
	
	return section;
}
/**
 * ======================================================================================
 * ATTRIBUTE
 * ======================================================================================
 */
/**
 * Return the value for the specified attribute name. If the specified attribute
 * does not exists, return the current value of the specified variable.
 * Parameters:
 * @param {Object} map	the hash map for attribute name and its value
 * @param {String} attr_name the specified attribute name
 * @param {Object} curr_var	 the current value of the attribute
 * Returns:
 * @return {Object} the value of the attribute
 */
private function getAttrValue (map:Object, attr_name:String, curr_var:Object):Object {
	if (map[attr_name] != null) {
		return map[attr_name];
	}
	return curr_var;
}
/**
 * Given the current node, return associative array of the recognized attribute
 * and its value
 * Parameters:
 * @param {XMLNode} currNode the current node
 * Returns:
 * @return {Object} attribute hash map (attrName, attrValue)
 */
private function getAttrMapFor (currNode:XMLNode):Object {
	var attr:Object = currNode.attributes;
	var map:Object = new Object();
	var map_text:String = "";
	for (var attr_name:String in attr) {
		var vn:String = TextUtil.trim(attr[attr_name]);
		var an:String = attr_name.toLowerCase();//the attribute name
		var ev:Object;//the extracted attribute value
		if (TextUtil.strIsEmpty(vn,true) == false) {
			ev = vn;
			if (an == "filename") {//for section and newpage tag
				ev = TextUtil.removeEmptyStrings((TextUtil.unescapeQuotes(vn)).split(";"));
			} else if ((an == "linebreak") || (an == "pagebreak")) {//for section tag
				ev = TextUtil.stringToBoolean(vn);
			} else if (an == "addtitle") {//whether to add section title to text
				ev = TextUtil.stringToBoolean(vn);
			} else if (an == "addnumber") {//whether to add section number to text
				ev = TextUtil.stringToBoolean(vn);
			} else if ((an == "bold") || (an == "italic") || (an == "added")) {//for section tag
				ev = TextUtil.stringToBoolean(vn);
			} else if (an == "src") {//for image tag
				ev = vn;
			} else if ((an == "width") || (an == "height")) {//for image tag
				ev = Math.abs(Number(vn));
			} else if (an == "align") {//for paragraph tag
				vn = TextUtil.replaceString (vn.toLowerCase(), "justify", "left");
				vn = TextUtil.replaceString (vn, "centre", "center");
				ev = vn;
			} else if (an == "href") {//it's an hyperlink
				if (vn.charAt(0) == '#') {//an internal link
					var link_name:String = vn.substring(1, vn.length);
					vn = "asfunction:_root.gotoLink," + link_name;
				}
				ev = vn;
			} else if ((an == "name") || (an == "title") || 
					   (an == "color") || (an == "id")) {
				ev = vn;//for link tag
			} else if ((an == "synonym") || (an == "active")) {
				ev = TextUtil.stringToBoolean(vn);
			} else if ((an == "lineindex") || (an == "x") || 
						(an == "y") || (an == "leading")) {
				ev = Number(vn);
			} else if ((an == "size") || (an == "point-size") ||
					   (an == "pointsize")) {//the size of the font
				an = "size";
				var font_size:Number = getFontSize (vn);
				ev = "" + font_size; 
			} else if ((an == "face") || (an == "font")) {//the name of the font
				var f_val:String = getFontFace(vn);
				ev = f_val;
			} else if ((an == "leftmargin") || (an == "rightmargin") ||
					   (an == "blockindent") || (an == "indent") ||
					   (an == "tabstops") || (an == "span")) {
				//for attribute to be ignored
				ev = null;
			}
			
			//add to the map
			if (ev != null) {
				map[an] = ev;
				map_text += " " + an + "='" + ev + "'";
			}
		}
	}
	map["map_text"] = map_text;
	return map;
}
/**
 * Get the appropriate font size
 * Parameters:
 * @param {String} vn the font attribute value string
 * Returns:
 * @return {Number} the font size number
 */
private function getFontSize (vn:String):Number {
	vn = TextUtil.replaceMultString(["pt","px"," "], vn.toLowerCase(), "");
	
	var isAdd:Number = 0;
	//we need to change the relative font size to fixed one
	if (vn.indexOf("+") == 0) {
		vn = TextUtil.replaceString(vn, "+", "");
		isAdd = 1;
	} else if (vn.indexOf("-") == 0) {
		vn = TextUtil.replaceString(vn, "-", "");
		isAdd = -1;
	}

	var font_size:Number = Number(vn);
	if (isNaN(font_size) == true) {
		font_size = 0;
	} else if (isAdd != 0) {
		var fSize:Number = 12;
		//the current line of the book
		var currLine:BookLine = currParagraph[currParagraph.length - 1];
		if (currLine != null)
			fSize = currLine.fontSize;
		font_size = fSize + (isAdd * font_size); 
	}
	font_size = Math.min(Math.max(1, font_size), txtField._width - 30);
	return font_size;
}
/**
 * Get the appropriate font face
 * Parameters:
 * @param {String} vn the font attribute value string
 * Returns:
 * @return {String} the font face
 */
private function getFontFace (vn:String):String {
	var f_arr:Array = TextUtil.removeEmptyStrings((vn.toLowerCase()).split(","));
	var f_val:String = "";
	for (var fi:Number = 0; fi < f_arr.length; fi++) {
		var fFace:String = f_arr[fi];
		if (TextUtil.findTerm(fontArray, fFace) == true) {
			if (f_val != "") 
				f_val += ", ";

			f_val += fFace;
		}
	}
	if (TextUtil.strIsEmpty(f_val,true) == true) {
		f_val = "arial";
	}
	
	return f_val;
}
/**
 * ======================================================================================
 * SECTION HEADER
 * ======================================================================================
 */
/**
 * Initialize the default global header style for the specified level
 * Parameters:
 * @param {Number} lvl  the header level we want to set
 */
private function initHeaderStyle (lvl:Number):Void {
	var font_size:Number = 12;
	var font_face:String = "Arial";
	var sec_italic:Boolean = false;
	var sec_bold:Boolean = true;
	var currLine:BookLine = currParagraph[currParagraph.length - 1];
	if (currLine != null) {//get the current text format for the body text
		var tst:String = currLine.lineStartStyle + "h" + currLine.lineEndStyle;
		txtField.htmlText = tst;
		var my_fmt:TextFormat = txtField.getTextFormat(0, 1);
		font_size = my_fmt.size;
		font_face = my_fmt.font;
	}

	//increase the font size
	font_size += Math.max((5 - lvl) * 2, 0);
	font_size = Math.min(Math.max(1, font_size), txtField._width - 30);
	
	var sec_break:Boolean = true;
	var sec_align:String = "center";
	var sec_num:Boolean = true;
	if ((lvl <= 1) || (lvl > 4)) {
		sec_num = false;
	}
	if (lvl > 2) {
		sec_align = "left";
		sec_break = false;
	}
	bookProp.headerStyle[lvl] = {align:sec_align, bold:sec_bold, face:font_face, 
								 italic:sec_italic, size:font_size, pagebreak:sec_break,
								 addnumber:sec_num};
}
/**
 * Write the title of the current section to the page
 * Parameters:
 * @param {BookSection} section	the current section
 */
private function writeSectionTitle (section:BookSection):Void {
 	var s_text:String = "<soc>";
	if (section.section_addtitle == true) {
		//add the section title to the top of the window browser	
		if ((TextUtil.strIsEmpty(bookProp.bookTitle,true) == true) &&
			(section.section_level == 1)) {
			bookProp.bookTitle = section.section_title;
		}
		//add section title to the page
		if ((section.section_slidesname == null) ||
			(section.section_slidesname.length == 0)) {
			s_text += "<p align='" + section.section_align + "'>";
			s_text += "<br />";
			s_text += "<font size='" + section.section_fsize + 
					  "' face='" + section.section_font + "'>";
			if (section.section_bold == true)
				s_text += "<b>";
			if (section.section_italic == true)
				s_text += "<i>";
			if (TextUtil.strIsEmpty(section.section_tnum,true) == false)
				s_text += section.section_tnum + " ";
			s_text += section.section_title;
			if (section.section_bold == true)
				s_text += "</b>";
			if (section.section_italic == true)
				s_text += "</i>";
			s_text += "</font>";
			if (section.section_level < 1)
				s_text += "<br />";
			s_text += "<br /></p>";
		}
	}
	s_text += "</soc>";
	
	var tempNode:XML = new XML(s_text);
	parseTreeHTMLNode(tempNode.childNodes[0], section);
 }
/**
 * Get the new section number text if a new section is added to the
 * given section
 * Parameters:
 * @param {BookSection} secParent the given section
 * Returns:
 * @return {Array} the section number text and the section counter
 */
private function setSectionNumber (secParent:BookSection):Array {
 	var s_num:String = "";
	var sub_num:Number = 1;
	if (secParent != null) {
		//sub_num = secParent.subsection.length + 1;
		for (var i:Number = secParent.subsection.length - 1; i >= 0; i--) {
			if (secParent.subsection[i].section_addnumber == true) {
				sub_num = secParent.subsection[i].section_num + 1;
				break;
			}
		}
		s_num += "" + sub_num;
		
		if (TextUtil.strIsEmpty(secParent.section_tnum,true) == false) {
 			return [(secParent.section_tnum + "." + sub_num), sub_num];
 		}
	}
	
 	/*if ((secParent != null) && (secParent.section_level >= 1)) {
 		s_num += (secParent.subsection.length + 1);
 		if (TextUtil.strIsEmpty(secParent.section_tnum,true) == false) {
 			return (secParent.section_tnum + "." + s_num);
 		}
 	}*/
 	return [s_num,sub_num];
}
/**
 * Given the current description tag, set the global variables
 * Parameters:
 * @param {XMLNode} 	currNode the description node
 * @param {BooKSection} section	 the current section
 */
private function parseDescriptionTag (currNode:XMLNode, section:BookSection):Void {
	for (var mI:Number = 0; mI < currNode.childNodes.length; mI++) {
		var metadataNode:XMLNode = currNode.childNodes[mI];
		var attr:Object = metadataNode.attributes;
		for (var attrOrig_name:String in attr) {
			var lCase = attrOrig_name.toLowerCase();
			attr[lCase] = attr[attrOrig_name];
		}
		var an = TextUtil.trim(attr["name"].toLowerCase());
		var vn = "";
		for (var w:Number = 0; w < metadataNode.childNodes.length; w++) {
			var s:String = TextUtil.trim(metadataNode.childNodes[w].toString());
			vn += s;
		}
		
		if ((an == "greenstone") ||(an == "showbookmarkuser") ||
			(an == "keepflipping") || (an == "allowprint") || 
			(an == "allownote") || (an == "allowbookmark") ||
			(an == "allowsave") || (an == "allowoption") || 
			(an == "allowanimate") || (an == "allowsearch") ||
			(an == "allowflipping") || (an == "allowthumbnail") || 
			(an == "allowzoom") || (an == "allownavigation")) {
			bookProp.addDescription(an,vn);
		} else if (an == "allowrunningheader") {
			bookProp.addRunningHeader = TextUtil.stringToBoolean(vn);
		} else if (an == "allowpagenumber") {
			bookProp.addPageNumber = TextUtil.stringToBoolean(vn);
		}
	
		if (TextUtil.strIsEmpty(vn,true) == false) {
			if ((an == "filename") || (an == "pagefolder") ||
				(an == "startnumbering") || (an == "zoomstyle") ||
				(an == "pagecolour") || (an == "covercolour") ||
				(an == "bookmargin") || (an == "xedgeoffset") ||
				(an == "yedgeoffset") || (an == "openatpage") ||
				(an == "paperback") || (an == "pagetype") ||
				(an == "shadow") || (an == "bookmarkbasecolour") ||
				(an == "tabstyle") || (an == "snapto") ||
				(an == "bookmarkcutoff") || (an == "waittofront") || 
				(an == "backpause") || (an == "turnpause") ||
				(an == "frontpause") || (an == "gotopage") ||
				(an == "flipspeed") || (an == "hidetoolbar")) {
				bookProp.addDescription(an,vn);
			} else if (an == "title") {
				//get the title name of the section
				for (var w:Number = 0; w < metadataNode.childNodes.length; w++) {
					var s:String = metadataNode.childNodes[w].toString();
					s = TextUtil.cleanSpaceFromText(s);
					section.section_title += s;
				}
				writeSectionTitle (section);
			} else if (an == "pagesize") {
				var xs:Array = vn.split("x");
				var w:Number = Math.abs(Number(TextUtil.trim(xs[0])));
				var h:Number = Math.abs(Number(TextUtil.trim(xs[1])));
				if ((xs.length == 2) && (isNaN(w) == false) && 
					(isNaN(h) == false)) {
					bookProp.setBookSize(w,h,txtField,txtFmt);
				}
			} else if (an == "topmargin") {
				bookProp.top_margin = Math.abs(Number(vn)); 
			} else if (an == "bottommargin") { 
				bookProp.bottom_margin = Math.abs(Number(vn));
			} else if (an == "leftmargin") {
				bookProp.left_margin = Math.abs(Number(vn));
			} else if (an == "rightmargin") {
				bookProp.right_margin = Math.abs(Number(vn));
			} else if (an == "orphancount") { 
				bookProp.orphanLine = Math.abs(Number(vn));
			} else if (an == "widowcount") {
				bookProp.widowLine = Math.abs(Number(vn));
			} else if ((an == "headerface") || (an == "pagenumberface")) {
				var font_face:String = getFontFace (vn);
				if (an == "headerface")
					bookProp.headerFace = font_face;
				else
					bookProp.numberFace = font_face;
			} else if ((an == "headercolour") || (an == "pagenumbercolour")) {
				if (an == "headercolour")
					bookProp.headerColor = vn;
				else
					bookProp.numberColor = vn;
			} else if ((an == "headersize") || (an == "pagenumbersize")) {
				var font_size:Number = getFontSize (vn);
				if (an == "headersize")
					bookProp.headerSize = font_size;
				else
					bookProp.numberSize = font_size;
			}  
		}
	}
}
/**
 * Parse the section tag
 * Parameters:
 * @param {XMLNode} currNode current XML node we want to parse
 * @param {BookSection} section the current book section we are building
 * Returns:
 * @return {BookSection} the new section once the end section tag is encountered 
 */
private function parseSectionTag (currNode:XMLNode, section:BookSection):BookSection {
	completeCurrParagraph();//write all the paragraph to the current page
	
	var currName:String = (currNode.nodeName).toLowerCase();
	var csec:BookSection = section;//the section parent
	var s_level:Number = section.section_level + 1;
	if (currName.charAt(0) == "h") {//get the section level from the header
		s_level = Number(currName.substr(1));
		while ((csec != null) && (csec.section_level >= s_level)) {
			csec = csec.section_parent;
		}
		if ((csec.section_level + 1) == s_level) {
			s_level = csec.section_level + 1;
		}
	} /*else if ((currName == "section") && 
			   (TextUtil.strIsEmpty(section.section_title) == true)) {
		s_level--;
	}*/
	var tnum:Array = setSectionNumber(csec);
	var s_tnum:String = "" + tnum[0];
	var s_num:Number = Number(tnum[1]);
	
	if (bookProp.headerStyle[s_level] == null) {//set the header style
		initHeaderStyle(s_level);
	}
	
	//create the new section
	var new_section = new BookSection(s_tnum, "", s_level);
	csec.subsection.push(new_section);
	new_section.section_parent = csec;
	
	//get all the possible attribute values of the section tag
	new_section.section_level = s_level;
	new_section.section_num = s_num;
	var map:Object = getAttrMapFor (currNode);
	new_section.section_align = getAttrValue(map, "align", 
											 bookProp.headerStyle[s_level].align);
	new_section.section_addtitle = getAttrValue(map, "addtitle", true);
	new_section.section_addnumber = getAttrValue(map, "addnumber",
												 bookProp.headerStyle[s_level].addnumber);
	if (new_section.section_addnumber == false) {
		new_section.section_tnum = "";
		new_section.section_num = 0;
	}
	new_section.section_bold = getAttrValue(map, "bold", 
											 bookProp.headerStyle[s_level].bold);
	new_section.section_font = getAttrValue(map, "face", 
											 bookProp.headerStyle[s_level].face);
	new_section.section_italic = getAttrValue(map, "italic", 
											 bookProp.headerStyle[s_level].italic);
	new_section.section_fsize = getAttrValue(map, "size", 
											 bookProp.headerStyle[s_level].size);
	new_section.section_newpage = getAttrValue(map, "pagebreak", 
												   bookProp.headerStyle[s_level].pagebreak);
	var slide_filenames:Array = (new Array()).concat(getAttrValue(map, "filename", new Array()));
	if (slide_filenames.length > 0) {
		new_section.section_slidesname = slide_filenames;
	}
	
	//temporarily add this section to the page
	currPage.page_section.push(new_section);
	new_section.section_page = currPage;
	
	if (currName == "section") {//keep recurse down the tree
		var passSection:BookSection = parseTree(currNode, new_section);
		if (passSection.section_level != new_section.section_level) {
			//there's a header tag inside the section tag
			return passSection;
		}
		return section;
	} else {
		for (var w:Number = 0; w < currNode.childNodes.length; w++) {
			var s:String = currNode.childNodes[w].toString();
			s = TextUtil.cleanSpaceFromText(s);
			new_section.section_title += s;
		}
		writeSectionTitle (new_section);
		return new_section;
	}
}
/**
 * ======================================================================================
 * TEXT
 * ======================================================================================
 */
/**
 * Given the text node, write the text to the page
 * Parameters:
 * @param {XMLNode} currNode the text node
 */
private function parseTextTag (currNode:XMLNode):Void {
	var s_text:String = currNode.nodeValue;//get the node value inside the HTML tag
	//the current line of the book
	var currLine:BookLine = currParagraph[currParagraph.length - 1];
	if (currLine == null) {
		currLine = addALineToCurrParagraph(false);
	}
	
	s_text = TextUtil.cleanSpaceFromText(s_text);
	if (s_text.charAt(0) == ' ') {//remove additional space
		if ((currLine.plain_text.length == 0) || 
			(currLine.plain_text.charAt(currLine.plain_text.length - 1) == ' ')) {
			s_text = s_text.substring(1, s_text.length);
		}
	}
	
	txtField.htmlText =  currLine.lineStartStyle + ' ' + currLine.lineEndStyle;
	var spaceWidth:Number = txtField.textWidth;
	txtField.htmlText =  currLine.lineStartStyle + currLine.lineText + "W" + currLine.lineEndStyle;
	var charWidth:Number = txtField.textWidth - currLine.lineWidth;
	var maxWidth:Number = Math.floor(txtField._width - (3 * currLine.fontSize));
	
	//do the line breaking
	while (s_text.length > 0) {
		if (((s_text.length * charWidth) + currLine.lineWidth) <= maxWidth) {
			currLine = writeToLine (currLine, s_text, spaceWidth);
			s_text = "";
		} else {
			var estChar:Number = Math.floor((maxWidth - currLine.lineWidth)/charWidth);
			if (estChar == 0) {
				estChar = Math.floor(maxWidth/charWidth);
			}
			estChar = Math.min(estChar, s_text.length);
			var wI:Number = estChar;
			if (estChar < s_text.length) {
				//get the last word
				var spaceIndex:Number = s_text.lastIndexOf(" ", estChar);
				if (spaceIndex == -1) {
					//get the first word if it fits the text
					var spaceIndex2:Number = s_text.indexOf(" ", estChar);
					if (spaceIndex2 == -1)
						spaceIndex2 = s_text.length  - 1;
					
					var w2_text = s_text.substring(0, spaceIndex2 + 1);
					txtField.htmlText = currLine.lineStartStyle + 
										currLine.lineText + w2_text;
					if ((txtField.textWidth - currLine.lineWidth) <= maxWidth) {
						wI = spaceIndex2;
					}
				} else {
					wI = spaceIndex;
				}
			}
			
			var w_text:String = s_text.substring(0, wI + 1);
			currLine = writeToLine (currLine, w_text, spaceWidth);
			s_text = s_text.substr(wI + 1);
		}
	}
}
/**
 * Write the current text to the specified line. If it doesn't fit
 * it will write in a new line.
 * NOTE: THIS FUNCTION DOES NOT DO ANY TEXT SPLITTING
 * Parameters:
 * @param {BookLine} currLine	the current book line
 * @param {String} 	 w_text		the text to be written to the line
 * @param {Number}	 spaceWidth	the width for a space
 * Returns:
 * @return {BookLine} the book line filled with the text
 */
private function writeToLine (currLine:BookLine, 
							  w_text:String, spaceWidth:Number):BookLine { 
	if (w_text.length == 0) 
		return currLine;
	
	txtField.htmlText = currLine.lineStartStyle + currLine.lineText + 
						w_text + currLine.lineEndStyle;
	var w_width:Number = txtField.textWidth;//the current line width
	var w_height:Number = txtField.textHeight;
	var lw_width:Number = w_width;
	w_width = w_width - currLine.lineWidth;//the word width
	if (w_width == 0) {
		if (w_text == " ") {
			w_width = spaceWidth;
		} else {
			return currLine;
		}
	}
	
	var p_text = txtField.text.substr(currLine.plain_text.length);
	if (p_text.charAt(p_text.length - 1) == '\r')
		p_text = p_text.substring(0, p_text.length - 1);
	if (lw_width >= Math.floor(txtField._width - (3 * currLine.fontSize))) {
		//cannot fit into the same line
		//create a new line
		currLine.isLineBreak = true;
		if (currLine.lineText.charAt(currLine.lineText.length - 1) == ' ') {
			currLine.lineText = currLine.lineText.substr(0,
														 currLine.lineText.length - 1);
			currLine.plain_text = currLine.plain_text.substr(0,
															 currLine.plain_text.length - 1);
			currLine.lineWidth -= spaceWidth;
		}
		if (currLine.lineText.charAt(currLine.lineText.length - 1) == '>') {
			currLine.lineText += "&#160;";
			currLine.lineWidth += spaceWidth;
		}
		currLine = addALineToCurrParagraph(true);
		
		if (w_text.charAt(0) == ' ') {
			w_text = w_text.substring(1, w_text.length);
			p_text = p_text.substring(1, p_text.length);
		}
	}

	currLine.lineText += w_text;
	currLine.plain_text += p_text;
	currLine.lineWidth += w_width;
	currLine.lineHeight = w_height;
	if (isLink == true) {
		var lastLink:BookLink = currLine.lineLinks[currLine.lineLinks.length - 1];
		lastLink.linkWidth += w_width;
		lastLink.linkHeight = currLine.lineHeight;
		if (wikiID != "")
			wikiWord += w_text;
	}
	return currLine;
}
/**
 * ======================================================================================
 * IMAGE TAG
 * ======================================================================================
 */
/**
 * Rescale the image to fit the page
 * Parameters:
 * @param {String} img_name		image's source file URL 
 * @param {Number} img_width	image's width
 * @param {Number} img_height	image's height
 * Returns:
 * @return {String} the image html text
 */
private function setImageSize(img_name:String,img_width:Number,img_height:Number):String {
	var hspace:Number = 10;//the horizontal pad
	var vspace:Number = 10;//the vertical pad
	var scale_width:Number = 1;//the x scaling factor
	var scale_height:Number = 1;//the y scaling factor
	if (img_width >= txtField._width) {
		//image is wider than the page so x scale
		var new_img_width:Number = txtField._width - (2 * hspace);
		scale_width = new_img_width/img_width;
	} else {
		//figure has to be centered means we need to pad the space
		hspace = (txtField._width - img_width)/2;
	}
				
	if (img_height >= txtField._height) {
		//image is taller than the page so y scale
		var new_img_height:Number = txtField._height - (2 * vspace);
		scale_height = new_img_height/img_height;
	}
	
	//scale image with the smallest factor
	var scale:Number = Math.min(scale_width,scale_height);
	img_width = img_width * scale;
	img_height = img_height * scale;
	
	//the current line of the book
	var currLine:BookLine = currParagraph[currParagraph.length - 1];
	var breakS:String = "<textformat leading='2'><p align='left'>" +
						"<font size='2'" +
					    " face='" + currLine.fontFace + "'>&#160;";
	var breakE:String = "</font></p></textformat>";
	
	var num:Number = Math.ceil((img_height + (2 * vspace))/4) + 1;
	
	var break_text:String = "";//put break according to the size of the image
	var break_str:String = breakS + breakE;
	for (var t:Number = 0; t < num; t++)
		break_text += break_str;
	
	var img_txt:String = breakS + 
						"<img src ='" + img_name + 
				  		"' width ='" + img_width + "' height ='" + img_height + 
				  		"' hspace ='" + hspace + "' vspace ='" + vspace + 
				  		"' align ='left' />" + breakE + 
						"<toc>" + break_text + "</toc>";
	return img_txt;
}
/**
 * ======================================================================================
 * INLINE TAG
 * ======================================================================================
 */
/**
 * Parse all the inline tag (font, i, b, a, u, textformat, span)
 * Parameters:
 * @param {XMLNode} 	currNode the current node
 * @param {BookSection} section  the current section
 * Returns:
 * @return {BookSection} the expanded book section
 */
private function parseInlineTag (currNode:XMLNode, section:BookSection):BookSection {
	var currName:String = (currNode.nodeName).toLowerCase();//the tag name
	//get the attributes of each tag
	var map:Object = getAttrMapFor (currNode);
	var s_attr:String = map["map_text"];//write the attributes in a string	
	
	var old_start_style:String = start_style;
	var old_end_style:String = end_style;
	var old_text_align:String = text_align;
	var old_line_leading:Number = line_leading;
	
	if (currName == "textformat") {
		line_leading = Number(getAttrValue(map, "leading", 2));
		s_attr = " leading='" + line_leading + "'";
	}
	
	//the current line of the book
	var currLine:BookLine = currParagraph[currParagraph.length - 1];	
	if (currParagraph.length == 0) {
		currLine = addALineToCurrParagraph(false);//create a new line
	}
	
	var sTag:String = "<" + currName + s_attr +">";
	var eTag:String = "</" + currName + ">";
	
	var old_font_size:Number = currLine.fontSize;
	var old_font_face:String = currLine.fontFace;
	var old_font_color:String = currLine.fontColor;
    if (currName == "font") {
		var font_size:Number = Number(getAttrValue(map, "size", old_font_size));
		var font_face:String = "" + getAttrValue(map, "face", old_font_face);
		var font_color:String = "" + getAttrValue(map, "color", old_font_color);
		var font_added:Boolean = Boolean(getAttrValue(map, "added", false));
		if (font_added == true) {
			section = parseTree(currNode, section);
			return section;
		}
		currLine.fontSize = font_size;
		currLine.fontFace = font_face;
		currLine.fontColor = font_color;
	} else if (currName == "a") {
		var linkURL:String = "" + getAttrValue(map, "href", "");//link destination
		var linkStatus:Boolean = Boolean(getAttrValue(map, "active", true));//link active status
		var linkSynonym:Boolean = Boolean(getAttrValue(map, "synonym", false));//find synonym
		var linkName:String = "" + getAttrValue(map, "name", "");//internal link name
		if (TextUtil.strIsEmpty(linkName,true) == true) {
			linkName = "" + getAttrValue(map, "id", "");
		}
		var overText:String = "" + getAttrValue(map, "title", "");//mouse over text
		if (TextUtil.strIsEmpty(linkURL,true) == true) {
			linkStatus = false;
		}
		
		isLink = true;
		if ((TextUtil.strIsEmpty(overText,true) == false) ||
			(TextUtil.strIsEmpty(linkName,true) == false) ||
			(linkSynonym == true)) {
			if ((overText.indexOf("wikiid:") == 0) && 
				(map["synonym"] == null)) {
				wikiID = overText; 
				if (bookProp.wikiList[wikiID] == null) {
					bookProp.wikiList[wikiID] = [];
					if (TextUtil.strIsEmpty(linkURL,true) == false) {
						var wa:String = "www.wikipedia.org/";
						var wi:Number = linkURL.indexOf(wa);
						if (wi != -1) {
							var wtitle:String = linkURL.substr(wi + wa.length);
							wtitle = TextUtil.replaceString(wtitle, "/", "");
							wtitle = TextUtil.replaceString(wtitle, "_", " ");
							bookProp.wikiList[wikiID].push(wtitle);
						}
					}
				}
				if (linkSynonym == false) {
					sTag = TextUtil.replaceString(sTag,"<a ", 
										 "<a synonym='true' ");
					linkSynonym = true;
				}
			} else if (linkSynonym == true) {
				wikiID = linkURL;
				if (bookProp.wikiList[wikiID] == null) {
					bookProp.wikiList[wikiID] = [];
				}
			}
		}
		
		if (linkStatus == true) {
			if (map["active"] == null) {
				sTag = TextUtil.replaceString(sTag, "<a ", "<a active='true' ");
			}
		}
		
		//we need to add the font color
		if (TextUtil.strIsEmpty(linkName,true) == true) { 
			sTag += "<font added='true' color='#0000FF'>";
			eTag = "</font>" + eTag;
			if (overText.indexOf("wikiid:") != 0) {//wiki link
				sTag += "<u>";
				eTag = "</u>" + eTag;
			}
		}
			
		var bl:BookLink = new BookLink();
		bl.linkText = overText;
		bl.linkName = linkName;
		bl.linkX = currLine.lineWidth;
		bl.linkSynonym = linkSynonym;
		bl.linkStatus = linkStatus;
		bl.linkURL = linkURL;
		currLine.lineLinks.push(bl);
	}
	
	writeStartTagToText (sTag, eTag);
	section = parseTree(currNode, section);
	writeCloseTagToText (eTag, old_end_style);
	
	if (currName == "font") {
		currLine = currParagraph[currParagraph.length - 1];
		currLine.fontSize = old_font_size;
		currLine.fontFace = old_font_face;
		currLine.fontColor = old_font_color;
	} else if (currName == "a") {
		isLink = false;
	
		wikiWord = wikiWord.toLowerCase();
		if ((TextUtil.strIsEmpty(wikiWord,true) == false) && (bookProp.wikiList[wikiWord] == null)) {
			bookProp.wikiList[wikiWord] = wikiID;
			bookProp.wikiList[wikiID].push(wikiWord);
		}
		wikiWord = "";
		wikiID = "";
	}
	
	start_style = old_start_style;
	end_style = old_end_style;
	text_align = old_text_align;
	line_leading = old_line_leading;
	
	return section;
}
/**
 * ======================================================================================
 * BLOCK TAG
 * ======================================================================================
 */
/**
 * Parse all the paragraph tag (img, p, br, li, soc)
 * Parameters:
 * @param {XMLNode} 	currNode the current node
 * @param {BookSection} section	 the current section
 * Returns:
 * @return {BookSection} the expanded section
 */
private function parseParagraphTag (currNode:XMLNode, section:BookSection):BookSection {
	var currName:String = (currNode.nodeName).toLowerCase();//the tag name
	//get the attributes of each tag
	var map:Object = getAttrMapFor (currNode);
	var s_attr:String = map["map_text"];//write the attributes in a string	
			
	var old_start_style:String = start_style;
	var old_end_style:String = end_style;
	var old_text_align:String = text_align;
	var old_line_leading:Number = line_leading;
	
	if (currName == "p") {
		text_align = "" + getAttrValue(map, "align", "left");
		s_attr = " align='" + text_align + "'";
	}
	
	writeParagraphToPage();//we need to write all the lines before this tag
	
	//the current line of the book
	var currLine:BookLine = currParagraph[currParagraph.length - 1];	
	if (currParagraph.length == 0) {
		currLine = addALineToCurrParagraph(false);//create a new line
	}
	
	if (currName == "img") {
		var img_name:String = "" + getAttrValue(map, "src", "");//image url
		var img_width:Number = Number(getAttrValue(map, "width", 0));//image width
		var img_height:Number = Number(getAttrValue(map, "height", 0));//image height
		
		currLine.lineStartStyle = "";
		currLine.lineEndStyle = "";
		currLine.lineLeading = 2;
		
		//add the modified image tag to the section's content
		var img_text:String = setImageSize(img_name,img_width,img_height);
		section.section_img_counter++;//increment the image counter for this section
		var img_title:String = "Figure " + section.section_tnum + "." + section.section_img_counter + ":" + img_name;
		var bimg:BookImage = new BookImage(img_title);//create a BookImage object 

		txtField.htmlText = img_text;
		var w_height:Number = txtField.textHeight;
		var w_width:Number = txtField.textWidth;
		
		currLine.lineText += img_text;
		currLine.plain_text += txtField.text;
		currLine.lineWidth = w_width;
		currLine.lineHeight = w_height;//Math.max(currLine.lineHeight, w_height);
		currLine.lineImage = bimg;
		var lastLink:BookLink = currLine.lineLinks[currLine.lineLinks.length - 1];
		if (lastLink != null) {
			lastLink.linkWidth = w_width;
			lastLink.linkHeight = currLine.lineHeight;
		}
	} else {
		var sTag:String = "<" + currName + s_attr +">";
		var eTag:String = "</" + currName + ">";
		
		if ((currName == "br") && (isParBlockInStart() == true)) {
			return section;
		}
		
		if (currName == "soc") {
			currLine.lineSection = section;
			currSection = section;
		} else if (currName == "bobitem") {
			bobNumber = Math.abs(bobNumber) + 1;
		}
		
		writeStartTagToText (sTag, eTag);
		section = parseTree(currNode, section);
		writeCloseTagToText (eTag, old_end_style);
		
		if (currName == "bobitem") {
			bobNumber = -1 * Math.abs(bobNumber);
		}
	}
	
	//we need to write all the lines in this tag
	writeParagraphToPage();
	
	if (currName == "soc") {
		currSection = null;
	}
	
	start_style = old_start_style;
	end_style = old_end_style;
	text_align = old_text_align;
	line_leading = old_line_leading;
	
	return section;
} 
/**
 * ======================================================================================
 * ANY TAG
 * ======================================================================================
 */
 /**
  * Whether there is a paragraph or list tag already in the start tag
  * Returns:
  * @return {Boolean} whether there is a <p> or <li> tag
  */
private function isParBlockInStart ():Boolean {
	var block_start:Array = ["<p", "<li"];
	return TextUtil.findTerm(block_start,start_style);
}
/**
 * Set the width and height of the specified line
 * Parameters:
 * @param {BookLine} currLine the specified line
 */
private function setLineProp (currLine:BookLine):Void {
	txtField.htmlText = currLine.lineStartStyle + currLine.lineText + currLine.lineEndStyle;
	currLine.lineWidth = txtField.textWidth;
	currLine.lineHeight = txtField.textHeight;
}
/**
 * Write the current line tag, given the starting tag and ending tag
 * Parameters:
 * @param {String} sTag start tag
 * @param {String} eTag end tag
 */
private function writeStartTagToText (sTag:String, eTag:String):Void {
	start_style = start_style + sTag;
	end_style = eTag + end_style;

	//the current line of the book
	var currLine:BookLine = currParagraph[currParagraph.length - 1];
	
	currLine.lineText += sTag;
	currLine.lineEndStyle = eTag + currLine.lineEndStyle;
	setLineProp(currLine);
	currLine.lineLeading = line_leading;//(txtField.getTextFormat(0,1)).leading;
}
/**
 * Write the closing tag to text
 * Parameters:
 * @param {String} eTag 			end tag
 * @param {String} old_end_style	the previous end style
 */
private function writeCloseTagToText (eTag:String, old_end_style:String):Void {
	//the current line of the book
	var currLine:BookLine = currParagraph[currParagraph.length - 1];
	if (currParagraph.length == 0) {//we have written all the text
		//the end tag should just be appended to the last line
		//commited to the page
		var lastPar:Array = currPage.page_paragraphs[currPage.page_paragraphs.length - 1];
		currLine = lastPar[lastPar.length - 1];
		if (currLine.lineImage != null)
			return;
	}
	
	currLine.lineText += eTag;
	currLine.lineEndStyle = old_end_style;
}
/**
 * Write each lines inside the paragraph to the book pages.
 */
function writeParagraphToPage () {
	if (currParagraph.length == 0) {
		return;
	} else if (currParagraph.length == 1) {
		var currLine:BookLine = currParagraph[currParagraph.length - 1];
		var txt = currLine.lineStartStyle + currLine.lineText + currLine.lineEndStyle;
		if (TextUtil.strIsEmpty(txt,true) == true)
			return;
	}
	
	//we need to combine all the lines from multiple paragraph
	//that belongs to the same line section
	if (currPage.page_paragraphs.length > 0) {
		var lPar:Array = currPage.page_paragraphs[currPage.page_paragraphs.length - 1];
		var lLine:BookLine = lPar[lPar.length - 1];
		var cLine:BookLine = currParagraph[0];
		
		if (((cLine.lineSection != null) && (cLine.lineSection == lLine.lineSection)) ||
			((cLine.bobNumber > 0) && (lLine.bobNumber == cLine.bobNumber))) {
			currPage.page_paragraphs[currPage.page_paragraphs.length - 1] = lPar.concat(currParagraph);
			currParagraph = new Array();
			return;
		}
	}
	currPage.page_paragraphs.push(currParagraph);
	currParagraph = new Array();
}
/**
 * Copy the font style of a line to another line
 * Parameters:
 * @param {BookLine} srcLine the line to be copied from
 * @param {BookLine} destLine the line copying
 */
private function copyFontStyle (srcLine:BookLine, destLine:BookLine) {
	destLine.fontSize = srcLine.fontSize;
	destLine.fontFace = srcLine.fontFace;
	destLine.fontColor = srcLine.fontColor;
}
/**
 * Add a new line to the current paragraph
 * Parameters:
 * @param {Boolean} removeLink whether to remove the last link
 * 						       that do not have text inside it
 * Returns:
 * @return {BookLine} the new line
 */
private function addALineToCurrParagraph(removeLink:Boolean):BookLine {
	//the current line of the book
	var currLine:BookLine = currParagraph[currParagraph.length - 1];	
	var newLine:BookLine = new BookLine();
	newLine.lineStartStyle = start_style;
	newLine.lineEndStyle = end_style;
	newLine.lineSection = currSection;
	newLine.lineAlign = text_align;
	newLine.lineLeading = line_leading;
	if (bobNumber > 0)
		newLine.bobNumber = bobNumber;
	setLineProp(newLine);

	if (currLine != null) {
		copyFontStyle(currLine,newLine);
		
		if (isLink == true) {
			//this text belongs to a hyperlink with mouse over
			var lastLink:BookLink = currLine.lineLinks[currLine.lineLinks.length - 1];
			var newLink:BookLink = new BookLink();
			newLink.linkText = lastLink.linkText;
			newLink.linkURL = lastLink.linkURL;
			newLink.linkName = lastLink.linkName;
			newLink.linkStatus = lastLink.linkStatus;
			newLink.linkSynonym = lastLink.linkSynonym;
			newLine.lineLinks.push(newLink);
			if ((removeLink == true) && (lastLink.linkWidth == 0)) {
				currLine.lineLinks.pop();
				delete lastLink;
			}
		}
	} else if (currPage.page_paragraphs.length > 0) {
		var lastPar:Array = currPage.page_paragraphs[currPage.page_paragraphs.length - 1];
		var lastLine:BookLine = lastPar[lastPar.length - 1];
		copyFontStyle(lastLine, newLine);
	}
	currParagraph.push(newLine);

	return newLine;
}
/**
 * Create a new paragraph with a resetted tag
 * Returns:
 * @return {BookLine} The starting line from that paragraph
 */
private function completeCurrParagraph ():BookLine {
	//we need to write all the lines before this tag
	writeParagraphToPage();
	
	if (currParagraph.length == 0)
		return addALineToCurrParagraph(false);
	else
		return currParagraph[currParagraph.length - 1];
}
}